(function($) {
  
  var methods = {
    model: {
      type: null,
      id: null,
      class: null,
      params: null,
      parent: null,
      obj: null,
      evts: null,
      callback: null,
      set: null,
      use: null,
      context: null
    },
    init: function(obj) {
      var data = null;
      if($.type(obj) == 'array') {
	data = [];
	var roots = this.root;
	for(var i = 0; i<obj.length; i++) {
	  if(roots[i]) {
	    this.root = roots[i];
	    data.push(methods.createObj.call(this, obj[i]));
	  }
	}
	this.root = roots;
      } else if($.type(obj) == 'object') {
	//Sólo se va a setear para el primer elemento de la selección.
	if(this.root[0]) {
	  var roots = this.root;
	  data = methods.createObj.call(this, obj);
	  this.root = roots;
	}
      } else if($.type(obj) == 'string') {
	return methods.find.call(this, obj);
      }
      this.root.data(this.type, data)
    },
    _evts: function(evts, elem) {
      var listeners = {};
      for(var e in evts) {
	if($.type(evts[e]) == 'array') {
	  listeners[e] = [];
	  for(var i = 0; i < evts[e].length; i++) {
	    listeners[e].push(google.maps.event.addListener(elem.obj, e, evts[e][i]));
	  }
	} else {
	  listeners[e] = google.maps.event.addListener(elem.obj, e, evts[e]);
	}
      }
      return listeners;
    },
    //createObj: function(type, obj, parent) {
    createObj: function(obj, parent) {
      if(obj.type && this.type != obj.type) {
	if($[obj.type]) {
	  return $[obj.type].contextualize.call(this.root, function(obj, parent) {
	    return this.createObj.call(this, obj, parent);
	  }, obj, parent);
	} else {
	  return {};
	}
      }
      /*if($[type] && $.type($[type].contextualize) == 'function') {
	return $[type].contextualize.call(this, function(obj, parent) {*/
	  var $t = this;
	  
	  var elem = $.extend({}, this.model, {
	    parent: parent,
	    context: this,
	    set: function(name, elem) {
	      if(!elem && ($.type(name) == 'object' || $.type(name) == 'array')) {
		this.children=this.children||[];
		var r = $t.searchObj.call($t, name, this);
		r!=name && this.children.push(r);
	      } else {
		this[name] = $t.searchObj.call($t, elem, this);
	      }
	    },
	    type: this.type
	  });
	  
	  /*if(obj.id) {
	    $[type].dict[obj.id] = elem;
	  }*/
	  var className = '';
	  if(!obj.class) {
	    obj.class = this.defaultClass;//'Map';
	  }
	  
	  if(this.behaviour[obj.class] && this.behaviour[obj.class].altClass && (!obj.use || obj.use.altClass !== 'false')) {
	    className = this.behaviour[obj.class].altClass;
	  } else {
	    var e = true;
	    var c = (this.baseClass+'.'+obj.class).split('.');
	    var o = window;
	    for(var i = 0; i<c.length; i++) {
	      if(!o[c[i]]) {
		e = false;
		break;
	      }
	      o = o[c[i]];
	    }
	    if(!e) {
	      var c = obj.class.split('.');
	      var o = window;
	      for(var i = 0; i<c.length; i++) {
		if(!o[c[i]]) {
		  return obj;
		}
		o = o[c[i]];
	      }
	      className = obj.class;
	    } else {
	      className = this.baseClass+'.'+obj.class;
	    }
	  }
	  
	  //obj.class = $.maps.behaviour[obj.type].class;
	  if((!obj.hasOwnProperty('use') || !obj.use.hasOwnProperty('params') || obj.use.params) && this.behaviour[obj.class] && this.behaviour[obj.class].params) {
	    obj.params = this.behaviour[obj.class].params.call(this.root, obj);
	  } else {
	    if(obj.params) {
	      if($.type(obj.params) != 'array') {
		obj.params = [obj.params];
	      }
	    } else {
	      obj.params = [];
	    }
	  }
	  obj.params = this.searchObj.call(this, obj.params);
	  elem.obj = this.instance(className, obj.params);
	  elem.id = obj.id;
	  elem.class = obj.class;
	  
	  if(obj.class && this.behaviour[elem.class] && this.behaviour[elem.class].default && (!obj.use || obj.use.default !== 'false')) {
	    this.behaviour[elem.class].default.call(elem);
	  }
	  
	  for(var e in obj) {
	    /*switch(e) {
	      case "id":
	      case "use":
	      case "class":
	      case "params":
	      case "evts":
	      case "type":
	      case "callback":
		break;
	      default:
		elem[e] = $.obj.searchObj(type, obj[e], elem);
		break;
	    }*/
	    if(!this.model.hasOwnProperty(e)) {
	      elem[e] = this.searchObj.call(this, obj[e], elem);
	    }
	  }
	  if(obj.evts) {
	    elem.listeners = this._evts(obj.evts, elem);
	  }
	  if(obj.callback && $.type(obj.callback) == 'function' && (!obj.use || obj.use.callback !== 'false')) {
	    obj.callback.call(elem);
	  }
	  if(obj.use && obj.use.wrapper === false) {
	    return elem.obj;
	  }
	  return elem;
	/*}, obj, parent);
      }*/
    },
    instance: function(className, params) {
      var e = null;
      var ops = [];
      if($.type(params) == 'array') {
	for(var j = 0; j<params.length; j++) {
	  ops.push('params['+j+']');
	}
	eval('e = new '+className+'('+ops.join(', ')+')');
      } else {
	eval('e = new '+className+'(params)');
      }
      return e;
    },
    searchObj: function(obj, parent) {
      /*if($[type] && $.type($[type].contextualize) == 'function') {
	return $[type].contextualize.call(this, function(obj, parent) {*/
	  if($.type(obj) == 'array') {
	    var ret = [];
	    for(var i = 0; i < obj.length; i++) {
	      ret.push(this.searchObj.call(this, obj[i], parent));
	    }
	    return ret;
	  } else if ($.type(obj) == 'object') {
	    if(obj.class && $.type(obj.class == 'string')) {
	      return this.createObj.call(this, obj, parent);
	    } else if(obj.__proto__ == {}.__proto__) {
	      for(var i in obj) {
		obj[i] = this.searchObj.call(this, obj[i], parent);
	      }
	    }
	  } 
	  return obj;
	/*}, obj, parent);
      }
      return obj;*/
    },
    find: function(selector) {
      var response = [];
      var re = /^(#(\w+))?(\[(\w+)\W*=\W*("((\W*\w+\W*)+)"|'((\W*\w+\W*)+)')\W*\])?(\.(\w+))?(:(\w+))?$/
      var search = function(props) {
	var response = [];
	if($.type(this) == 'array') {
	  for(var i = 0; i < this.length; i++) {
	    var r = search.call(this[i], props);
	    if(r) {
	      response = response.concat(r);
	    }
	  }
	} else {
	  var found = true;
	  for(var i in props) {
	    if(this[i] != props[i]) {
	      found = false;
	      break;
	    }
	  }
	  
	  if(found) {
	    response.push(this);
	  }
	  
	  if(!found || !props.hasOwnProperty('id')) {
	    for(var i in this) {
	      if(!methods.model.hasOwnProperty(i) && (($.type(this[i]) == 'object' && this[i].__proto__ == {}.__proto__ && this[i].hasOwnProperty('class') && this[i].hasOwnProperty('type')) || $.type(this[i]) == 'array')) {
		  var r = search.call(this[i], props);
		  if(r) {
		    response = response.concat(r);
		  }
	      }
	    }
	  }
	}
	if(!response.length) {
	  return false;
	}
	return response;
      };
      
      var selscoma = selector.split(',');
      for(var i = 0; i < selscoma.length; i++) {
	var selspace = $(selscoma[i].trim().split(' ')).filter(function(){
	  return this != '';
	});
	var t = $(this.root).data(this.type);
	for(var j = 0; j < selspace.length; j++) {
	  var m = selspace[j].match(re);
	  if(m && m[0] != '') {
	    var props = {};
	    if(m[2]) {
	      props.id = m[2];
	    }
	    if(m[4]) {
	      props[m[4]] = m[6]||m[8];
	    }
	    if(m[11]) {
	      props.class = m[11];
	    }
	    if(m[13]) {
	      props.type = m[13];
	    }
	    t = search.call(t, props);
	  } else {
	    t = false;
	  }
	  if(!t) break;
	}
	if(t) {
	  response = response.concat(t);
	}
      }
      return response;
    },
    add: function(selector, object) {
      var elem;
      if(!object) {
	object = selector;
	elem = $(this.root).data(this.type);
      } else {
	elem = this.find.call(this, selector);
      }
      if($.type(elem) == 'array') {
	$(elem).each(function() {
	  this.set(object);
	});
      } else {
	elem.set(object);
      }
    }
  };
  
  $.hierarchy = {
    extend: function(obj) {
      return $.extend({}, methods, obj);
    }
  };
  
})(jQuery);